setClass("Geometry", slots = c(
  pointer = "externalptr"), 
  prototype = list(pointer=NULL)
)

setClass("GeoPoint", contains = "Geometry")

setClass("GeoLine", contains = "Geometry")

setClass("GeoRegion", contains = "Geometry")

setMethod("show", "Geometry",  function(object){
  id <- GetID(object)
  type <- GetType(object)
  print(object@pointer)
  str <- sprintf("Geomrtry id: %d, type: %s", id, type)
  print(str)
})

setMethod("show", "GeoPoint",  function(object){
  id <- GetID(object)
  xy <- GetInnerPoint(object)
  print(object@pointer)
  str <- sprintf("Geomrtry id: %d, type: point", id)
  print(str)
  str <- sprintf("x: %f, y: %f", xy[1,1], xy[1,2])
  print(str)
})

setMethod("show", "GeoLine",  function(object){
  id <- GetID(object)
  str <- sprintf("Geomrtry id: %d, type: line", id)
  print(object@pointer)
  print(str)
})

setMethod("show", "GeoRegion",  function(object){
  id <- GetID(object)
  str <- sprintf("Geomrtry id: %d, type region", id)
  print(object@pointer)
  print(str)
})

.valid.geometry.types <- c('point', 'line', 'region')

PrintGeometryType <- function(){
  print(.valid.geometry.types)
}

#' @export
.IsGeoTypeValid <- function(geoType){
  length(.valid.geometry.types[(.valid.geometry.types == tolower(geoType))]) == 1
}

CreateGeometry <- function(geoType) {
  if (is.character(geoType) && .IsGeoTypeValid(geoType)) {
    geo <- .iCall('R_Geometry_create', geoType, PACKAGE='RSuperMap')
    if (is.null(geo) || is.logical(geo)) {
      warning("creating geometry failed")
      return(NULL)
    } else {
      return(geo)
    }
  } else {
    stop("Error geometry type. The valid geometry type is (Point, Line,Region)")
  }
}

setGeneric("GetType", function(obj) {
  standardGeneric( "GetType" )
})

setMethod("GetType", "Geometry",  function(obj){
  .GetGeometryType(obj)
})

setMethod("GetType", "GeoPoint",  function(obj){
  .GetGeometryType(obj)
})

setMethod("GetType", "GeoLine",  function(obj){
  .GetGeometryType(obj)
})

setMethod("GetType", "GeoRegion",  function(obj){
  .GetGeometryType(obj)
})


.GetGeometryType <- function(obj) {
  if (!is.null(obj)) {
    res <- .iCall('R_Geometry_getType', obj@pointer, PACKAGE='RSuperMap')
    if (is.null(res) || is.logical(res)) {
      warning("getting geometry type failed")
      return(NULL)
    } else {
      return(tolower(res))
    }
  } else {
    stop("invalid obj, obj must be (Geometry, GeoPoint, GeoLine, GeoRegion) object")
  }
}

setGeneric("GetInnerPoint", function(obj) {
  standardGeneric( "GetInnerPoint" )
})

setMethod("GetInnerPoint", "Geometry",  function(obj){
  .GetInnerPoint(obj)
})

setMethod("GetInnerPoint", "GeoPoint",  function(obj){
  .GetInnerPoint(obj)
})

setMethod("GetInnerPoint", "GeoLine",  function(obj){
  .GetInnerPoint(obj)
})

setMethod("GetInnerPoint", "GeoRegion",  function(obj){
  .GetInnerPoint(obj)
})

.GetInnerPoint <- function(obj) {
  if (!is.null(obj) && .IsGeoTypeValid(GetType(obj))) {
    xy <- .iCall("R_Geometry_getInnerPoint", obj@pointer, PACKAGE="RSuperMap")
    if (!is.null(xy) && is.vector(xy) && length(xy) >= 2) {
      return(matrix(data=xy, nrow=1, ncol=2, dimnames = list(c(1L),c("x","y"))))
    } else {
      warning("computing inner point failure ")
      return(FALSE)
    }
  } else {
    stop("invalid object. must be (Geometry, GeoPoint, GeoLine, GeoRegion)")
  }
}

CreatePoint <- function(x, y) {
  geo = CreateGeometry("point")
  if (!is.null(geo) && !is.logical(geo)) {
    if(SetPoints(geo, x, y)){
      return(geo)
    } else {
      warning("setting point to geometry failure")
    }
  } else {
    warning("creating geometry failure")
  }
  return(NULL)
}

CreateLine <- function(xys, polys){
  if(is.null(xys) || !is.matrix(xys)){
    stop("xys is not a matrix")
  }
  if(ncol(xys) < 2){
    stop("xys's col number must be greater 2")
  }
  if(is.null(polys) || !is.vector(polys)){
    stop("polys is not a vector")
  }
  if(sum(polys) != length(xys[,1])){
    stop("polys sum is not equal points size")
  }
  geo = CreateGeometry("line")
  if(!is.null(geo) &&  !is.logical(geo)){
    if(SetPoints(geo, xys, polys)){
      return(geo)
    } else {
      warning("setting points to geometry failure")
    }
  } else {
    warning("creating geometry failure")
  }
  return(NULL)
}

CreateRegion <- function(xys, polys) {
  if (is.null(xys) || !is.matrix(xys)) {
    stop("xys is not a matrix")
  }
  if (ncol(xys) < 2) {
    stop("xys's col number must be greater 2")
  }
  if (is.null(polys) || !is.vector(polys)) {
    stop("polys is not a vector")
  }
  if (sum(polys) != length(xys[,1])) {
    stop("polys sum is not equal points size")
  }
  geo = CreateGeometry("region")
  if (!is.null(geo) && !is.logical(geo)) {
    if (SetPoints(geo, xys, polys)) {
      return(geo)
    } else {
      warning("setting points to geometry failure")
    }
  } else {
    warning("creating geometry failure")
  }
  return(NULL)
}

setGeneric("SetPoints", function(obj, ...) {
  standardGeneric( "SetPoints" )
})

setMethod("SetPoints", "Geometry", function(obj, xys, polys) {
  if (is.null(xys) || !is.matrix(xys)) {
    stop("xys is not a matrix")
  }
  if (ncol(xys) < 2) {
    stop("xys's col number must be greater 2")
  }
  if (is.null(polys) || !is.vector(polys)) {
    stop("polys is not a vector")
  }
  if (sum(polys) != length(xys[,1])) {
    stop("polys sum is not equal points size")
  }
  
  xs <- xys[,1]
  ys <- xys[,2]
  
  res <- .iCall("R_Geometry_make", obj@pointer, xs, ys, polys,  PACKAGE="RSuperMap")
  if (is.null(res) || is.logical(res)) {
    warning("making geometry failed")
    return(NULL)
  } else {
    return(res)
  }
})

setMethod("SetPoints", "GeoPoint", function(obj, x, y){
  if (is.null(obj)) {
    stop("obj must be valid")
  }
  if (!is.numeric(x) || !is.numeric(y)) {
    stop("x, y must be numberic")
  }
  res <- .iCall("R_GeoPoint_make", obj@pointer, x, y, PACKAGE="RSuperMap")
  if(res == FALSE){
    warning("making Point failed")
    return(FALSE)
  } else {
    return(TRUE)
  }
})

setMethod("SetPoints", "GeoLine", function(obj, xys, polys){
  if (is.null(obj)) {
    stop("obj must be valid")
  }
  if (is.null(xys) || !is.matrix(xys)) {
    stop("xys is not a matrix")
  }
  if (ncol(xys) < 2) {
    stop("xys's col number must be greater 2")
  }
  if (is.null(polys) || !is.vector(polys)) {
    stop("polys is not a vector")
  }
  if (sum(polys) != length(xys[,1])) {
    stop("polys sum is not equal points size")
  }
  
  xs <- xys[,1]
  ys <- xys[,2]
  
  res <- .iCall("R_Geometry_make", obj@pointer, xs, ys, polys, PACKAGE="RSuperMap")
  if (res == FALSE) {
    warning("making Line failed")
    return(FALSE)
  } else {
    return(TRUE)
  }
})

setMethod("SetPoints", "GeoRegion", function(obj, xys, polys){
  if (is.null(obj)) {
    stop("obj must be valid")
  }
  if (is.null(xys) || !is.matrix(xys)) {
    stop("xys is not a matrix")
  }
  if (ncol(xys) < 2) {
    stop("xys's col number must be greater 2")
  }
  if (is.null(polys) || !is.vector(polys)) {
    stop("polys is not a vector")
  }
  if (sum(polys) != length(xys[,1])) {
    stop("polys sum is not equal points size")
  }
  
  xs <- xys[,1]
  ys <- xys[,2]
  
  res <- .iCall("R_Geometry_make", obj@pointer, xs, ys, polys, PACKAGE="RSuperMap")
  if (res == FALSE) {
    warning("making Region failed")
    return(FALSE)
  } else{
    return(TRUE)
  }
})

.geometry.length <- function(obj){
  if (is.null(obj)) {
    stop("obj must be valid")
  }
  res <- .iCall("R_Geometry_getLength", obj@pointer, PACKAGE="RSuperMap")
  if (is.null(res) || is.logical(res)) {
    warning("getting geometry length failed")
    return(NULL)
  } else {
    return(res)
  }
}

setGeneric("GetLength", function(obj){
  standardGeneric("GetLength")
})

setMethod("GetLength", "GeoLine", function(obj){
  .geometry.length(obj)
})

setMethod("GetLength", "GeoRegion", function(obj){
  .geometry.length(obj)
})


.geometry.id.get <- function(obj){
  if (is.null(obj)) {
    stop("obj must be valid")
  }
  res <- .iCall("R_Geometry_getID", obj@pointer, PACKAGE="RSuperMap")
  if (is.null(res) || is.logical(res)) {
    warning("getting geometry id failed")
    return(-1)
  } else {
    return(res)
  }
}

setGeneric("GetID", function(obj) {
  standardGeneric("GetID")
})

setMethod("GetID", "Geometry", function(obj){
  .geometry.id.get(obj)
})

setMethod("GetID", "GeoPoint", function(obj){
  .geometry.id.get(obj)
})

setMethod("GetID", "GeoLine", function(obj){
  .geometry.id.get(obj)
})

setMethod("GetID", "GeoRegion", function(obj){
  .geometry.id.get(obj)
})

.geometry.id.set <- function(obj, id){
  if (is.null(obj)) {
    stop("obj must be valid")
  }
  if (!is.integer(id)) {
    stop("id must be integer")
  }
  if (id < 1L) {
    stop("id must be greater 0")
  }
  res <- .iCall("R_Geometry_setID", obj@pointer,id, PACKAGE="RSuperMap")
  if (is.null(res) || is.logical(res)) {
    warning("setting geometry id failed")
    return(NULL)
  } else {
    return(res)
  }
}
setGeneric("SetID", function(obj,id){
  standardGeneric( "SetID" )
})

setMethod("SetID", "Geometry", function(obj,id){
 .geometry.id.set(obj,id)
})

setMethod("SetID", "GeoPoint", function(obj,id){
 .geometry.id.set(obj,id)
})

setMethod("SetID", "GeoLine", function(obj,id){
 .geometry.id.set(obj,id)
})

setMethod("SetID", "GeoRegion", function(obj,id){
 .geometry.id.set(obj,id)
})

setGeneric("GetPoints", function(obj){
  standardGeneric( "GetPoints" )
})

.spatial.points.get <- function(obj){
  if (is.null(obj)) {
    stop("obj must be valid")
  }
  
  points <- .iCall("R_Geometry_getPoints", obj@pointer, PACKAGE="RSuperMap")
  
  if (!is.null(points) && !is.logical(points) && is.vector(points) && length(points) == 3) {
    xs <- points[1]
    ys <- points[2]
    polyCounts <- points[3]
    pnts = matrix(nrow=length(xs[[1]]), ncol=2, dimnames = list(c(1:length(xs[[1]])),c("x","y")))
    pnts[, 1] = xs[[1]]
    pnts[, 2] = ys[[1]]
    return( list(pnts, polyCounts[[1]]) )
  } else {
    warning("getting points failure")
    return(NULL)
  }
}
setMethod("GetPoints", "Geometry", function(obj){
 .spatial.points.get(obj)
})

setMethod("GetPoints", "GeoPoint", function(obj){
  .GetInnerPoint(obj)
})

setMethod("GetPoints", "GeoLine", function(obj){
 .spatial.points.get(obj)
})

setMethod("GetPoints", "GeoRegion", function(obj){
 .spatial.points.get(obj)
})

.geometry.dimension <- function(obj){
  if (is.null(obj)) {
    stop("obj must be valid")
  }
  
  res <- .iCall("R_Geometry_getDimension", obj@pointer, PACKAGE="RSuperMap")
  if (is.null(res) || is.logical(res)) {
    warning("getting geometry dimension failed")
    return(FALSE)
  }
  else{
    return(res)
  }
}

setGeneric("GetDimension", function(obj){
  standardGeneric( "GetDimension" )
})

setMethod("GetDimension", "Geometry", function(obj){
  .geometry.dimension(obj)
})

setMethod("GetDimension", "GeoPoint", function(obj){
  .geometry.dimension(obj)
})

setMethod("GetDimension", "GeoLine", function(obj){
  .geometry.dimension(obj)
})

setMethod("GetDimension", "GeoRegion", function(obj){
  .geometry.dimension(obj)
})

.geometry.area <- function(obj){
  if (is.null(obj)) {
    stop("obj must be valid")
  }
  
  res <- .iCall("R_Geometry_getArea", obj@pointer, PACKAGE="RSuperMap")
  if (is.null(res) || is.logical(res)) {
    warning("getting geometry area failed")
    return(FALSE)
  }else{
    return(res)
  }
}

setGeneric("GetArea", function(obj){
  standardGeneric( "GetArea" )
})

setMethod("GetArea", "Geometry", function(obj){
  .geometry.area(obj)
})

setMethod("GetArea", "GeoRegion", function(obj){
  .geometry.area(obj)
})

.geometry.bounds <- function(obj){
  if (is.null(obj)) {
    stop("obj must be valid")
  }
  
  bounds <- .iCall("R_Geometry_getBounds", obj@pointer, PACKAGE="RSuperMap")
  if (!is.null(bounds) && !is.logical(bounds) &&  is.vector(bounds) && length(bounds) >= 4) {
    bbox <- .to.bounds(bounds)
    return(bbox)
  }else{
    warning("computing geometry bounds failure")
    return(NULL)
  }
}

setMethod("GetBounds", "Geometry", function(obj){
  .geometry.bounds(obj)
})

setMethod("GetBounds", "GeoPoint", function(obj){
   .geometry.bounds(obj) 
})

setMethod("GetBounds", "GeoLine", function(obj){
   .geometry.bounds(obj)
})

setMethod("GetBounds", "GeoRegion", function(obj){
  .geometry.bounds(obj)
})

setGeneric("GetSubCount", function(obj){
  standardGeneric( "GetSubCount" )
})

setMethod("GetSubCount", "Geometry", function(obj){
  .geometry.sub.count(obj)
})

setMethod("GetSubCount", "GeoPoint", function(obj){
  .geometry.sub.count(obj) 
})

setMethod("GetSubCount", "GeoLine", function(obj){
  .geometry.sub.count(obj)
})

setMethod("GetSubCount", "GeoRegion", function(obj){
  .geometry.sub.count(obj)
})

.geometry.sub.count <- function(obj){
  if (is.null(obj)) {
    stop("obj must be valid")
  }
  
  res <- .iCall("R_Geometry_sub_count", obj@pointer, PACKAGE="RSuperMap")
  if (is.null(res) || is.logical(res)) {
    warning("getting geometry sub count failed")
    return(FALSE)
  } else {
    return(res)
  }
}


CheckPolygon <- function(obj){
  if (is.null(obj)) {
    stop("obj must be valid")
  }
  if (class(obj) == "GeoRegion") {
    .iCall("R_GeoRegion_isHole", obj@pointer, PACKAGE="RSuperMap")
  } else {
    warning("obj must be GeoRegion object")
    return(FALSE)
  }
}

ProtectedDecompose <- function(obj){
  if (is.null(obj)) {
    stop("obj must be valid")
  }
  
  if (class(obj) == "GeoRegion") {
    res <- .iCall("R_GeoRegion_protected_decompose", obj@pointer, PACKAGE="RSuperMap")
    if (is.null(res) || is.logical(res)) {
      warning("protected decomposing region failed")
      return(NULL)
    } else {
      return(res)
    }
  } else {
    stop("obj must be GeoRegion object")
  }
}

